#include <cpu.h>
#include <isa.h>
#include <memory/vaddr.h>
#include <verilated_vcd_c.h>
#include "VRISCV_BOARD___024root.h"
#include <svdpi.h>
#include <sdb/watchpoint.h>
#include <trace/itrace.h>
#include <trace/ftrace.h>
#include <trace/mtrace.h>
#include <trace/isa-difftest.h>


vluint64_t sim_time = 0;
CPU_state cpu_data = {};
uint64_t inst_cnt =0;
extern VRISCV_BOARD* cpu;
extern mode launch_mode;

const char *regs[] = {
	"$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
	"s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
	"a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
	"s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};

/* STATUS */

void set_cpu_status(int state, vaddr_t pc, int halt_ret) {
	cpu_status.state = state;
	cpu_status.halt_pc = pc;
	cpu_status.halt_ret = halt_ret;
}

extern "C" void halt(svBit is_halt){
	//NPCTRAP(cpu->pc,10号寄存器($a0)的内容);
	if(is_halt){
		NPCTRAP(cpu_data.pc,gpr(10));
	}
	return;
}

extern VerilatedVcdC* tfp;
void cpu_terminate(){
	if(cpu_status.state == ABORT){//ABORT
		printf("\nNPC EXIT: \033[0m\033[1;31mABORT\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
	}else if(cpu_status.halt_ret == 0){//GOOD
		printf("\nNPC EXIT: \033[0m\033[1;32mHIT GOOD TRAP\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
	}else{//BAD or unexpected situation	
		IFDEF(CONFIG_ITRACE_COND,buffer_disp());
		printf("\nNPC EXIT: \033[0m\033[1;31mHIT BAD TRAP\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
	}
	IFDEF(CONFIG_TRACE,tfp->close());
	cpu->final();
	delete cpu;
	
}


/* DATA */

extern "C" void get_cpu_pc(int pc,int dnpc){
	cpu_data.pc = pc;
	cpu_data.dnpc = dnpc;
}

extern "C" void get_cpu_inst(int inst){
	cpu_data.inst = inst;
}

void get_cpu_reg(){
	VlUnpacked<IData,32> rf_struct = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__id_rf__DOT__rf;
	gpr(0) = 0;
	for(int i=1;i<REG_NUM;i++) gpr(i) = rf_struct.m_storage[i];
}


void reg_display() {
	printf(ANSI_FMT(" name   value   name   value   name   value   name   value\n",ANSI_FG_BLUE));
	for(int i=0;i<32;i+=4){
		printf(ANSI_FMT(" %3s 0x%08x %3s 0x%08x %3s 0x%08x %3s 0x%08x\n",ANSI_FG_BLUE), regs[i], gpr(i), regs[i+1], gpr(i+1), regs[i+2], gpr(i+2), regs[i+3], gpr(i+3));
	}
}

word_t reg_str2val(const char *s, bool *success) {
	for(int i=0;i<REG_NUM;i++){
		if(!strcmp(regs[i],s)){ 
			*success=true;
			return gpr(i);
		}
	}
	*success=false;
	return 0;
}

bool difftest_checkregs(CPU_state* cpu_data_ref, vaddr_t pc){
	bool flag = true;
	for(int i=1;i<REG_NUM;i++){
		if(gpr(i) != cpu_data_ref->gpr[i]){
			//pc = cpu_data_ref->pc;
			printf(ANSI_FMT("--register [%s] should be %#010x,but exactly it's %#010x--\n",ANSI_FG_BLUE),regs[i],cpu_data_ref->gpr[i],gpr(i));
			flag = false;
		}
	}
	return flag;

}


/* EXEC */

static void single_inst_debug(){
	//TRACE
	IFDEF(CONFIG_ITRACE_COND,buffer_insert());
	IFDEF(CONFIG_FTRACE_COND,elf_call(cpu_data.pc,cpu_data.dnpc,cpu_data.inst));
	
	//DIFFTEST(REF = NEMU)
	IFDEF(CONFIG_DIFFTEST_COND,difftest_step(cpu_data.pc,cpu_data.dnpc,cpu_data.inst==0));

	//watchpoint update
	if(launch_mode==SDB){	
		uint32_t new_result=0;
		WP* wp=check_wp(&new_result);
		if(wp!=NULL){
			cpu_status.state=STOP;
			printf(ANSI_FMT("Watchpoint change",ANSI_FG_GREEN) ":In No.%d,[%s],(%d==>%d)\n",wp->NO,wp->expr_s,wp->val,new_result);
			wp->val=new_result;
		}
	}
}


extern void clk_update();
void exec_once(){
	get_cpu_reg();
	IFDEF(CONFIG_STEP_INST,printf("%#010x:\t%#010x\n",cpu_data.pc,cpu_data.inst));
	clk_update();
	inst_cnt++;

}



void device_update();
void exec(uint64_t n){
	
	switch(cpu_status.state){
		case TERMINATE:case ABORT:
			printf("Program execution has ended. To restart the program, exit NPC and run again.\n");
			
		return;

		default://STOP、ALIVE
			cpu_status.state=ALIVE;
		break;
	}



#ifdef CONFIG_ITRACE_COND
	log_write("\n[START INST LOGGING]\n");
	log_write("   %+7s\t\t%+15s\t\t\t%+10s\n","PC","INST","INST-HEX");
#endif
	for(;n>0 && cpu_status.state == ALIVE;n--){
		exec_once();
		single_inst_debug();
		device_update();
		IFDEF(CONFIG_HOLD,if(cpu_data.pc==CONFIG_HOLD_PC) set_cpu_status(ABORT, cpu_data.pc, 1));
	}
#ifdef CONFIG_ITRACE_COND
	log_write("\n[TERMINATE INST LOGGING]\n");
#endif


	switch(cpu_status.state){
		case ALIVE:
			cpu_status.state = STOP;
		break;


		case ABORT:
			IFDEF(CONFIG_ITRACE_COND,printf("\n");buffer_disp());
			IFDEF(CONFIG_MTRACE_COND,printf("\n");mem_buffer_disp());
			cpu_terminate();
		
		case TERMINATE:
		case QUIT://TERMINATE、ABORT时也成立
			//TODO:输出统计信息

		break;
	}
}


